/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef HTTP2_HTTP2_COMMON_H
#define HTTP2_HTTP2_COMMON_H

#include "http2_handle_shake.h"

#define MAKE_NV(NAME, VALUE)                                                                                           \
    { (uint8_t *)(NAME), (uint8_t *)(VALUE), (sizeof(NAME) - 1), (sizeof(VALUE) - 1), NGHTTP2_NV_FLAG_NONE }

#define MAKE_NV_CS(NAME, VALUE)                                                                                        \
    { (uint8_t *)(NAME), (uint8_t *)(VALUE), (sizeof(NAME) - 1), (strlen(VALUE)), NGHTTP2_NV_FLAG_NONE }

enum { IO_NONE, WANT_READ, WANT_WRITE };

napi_env envMessage_ = nullptr;
napi_ref callBackRefMessage_ = nullptr;

void (*g_postEvent)(int type, void *data, int len, int reqType);

void MessageCallback(void (*postEvent)(int type, void *data, int len, int reqType)) { g_postEvent = postEvent; }

ssize_t SendCallback(nghttp2_session *session, const uint8_t *data, size_t length, int flags, void *userData)
{
    struct Connection *connection = nullptr;
    int rv = 0;
    connection = (struct Connection *)userData;
    connection->wantIo = IO_NONE;
    LOGE("send_callback fd:%d", connection->fd);
    if (IsDestroyed(connection->fd)) {
        return 0;
    }
    ERR_clear_error();
    int len = static_cast<int>(length);
    rv = SSL_write(connection->ssl, data, len);
    if (rv <= 0) {
        int err = SSL_get_error(connection->ssl, rv);
        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
            connection->wantIo = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
            rv = NGHTTP2_ERR_WOULDBLOCK;
        } else {
            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
        }
    }
    return rv;
}

ssize_t RecvCallback(nghttp2_session *session, uint8_t *buf, size_t length, int flags, void *userData)
{
    int rv = 0;
    struct Connection *connection = (struct Connection *)userData;
    LOGE("recv_callback %d", length);
    connection->wantIo = IO_NONE;
    if ((connection == nullptr) || (connection->ssl == nullptr) ||
        (buf == nullptr) || IsDestroyed(connection->fd)) {
        return 0;
    }
    ERR_clear_error();
    int len = static_cast<int>(length);
    rv = SSL_read(connection->ssl, buf, len);
    LOGE("recv_callback SSL_read %d", rv);
    if (rv < 0) {
        int err = SSL_get_error(connection->ssl, rv);
        if (err == SSL_ERROR_WANT_WRITE || err == SSL_ERROR_WANT_READ) {
            connection->wantIo = (err == SSL_ERROR_WANT_READ ? WANT_READ : WANT_WRITE);
            rv = NGHTTP2_ERR_WOULDBLOCK;
        } else {
            rv = NGHTTP2_ERR_CALLBACK_FAILURE;
        }
    } else if (rv == 0) {
        rv = NGHTTP2_ERR_EOF;
        LOGE("recv_callback NGHTTP2_ERR_EOF");
    }
    return rv;
}


void HandleGoawayFrame(void *userData, nghttp2_goaway goway)
{
    struct Connection *connection = (Connection *)userData;
    LOGE("HandleGoawayFrame fd:%d data:%s", connection->fd, (char *)goway.opaque_data);
    std::unordered_map<char *, void *> map;
    map["opaque_data"] = goway.opaque_data;
    map["error_code"] = (int *)goway.error_code;
    map["last_stream_id"] = (int *)goway.last_stream_id;
    map["fd"] = (int *)connection->fd;
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_GOWAY, &map, 0, connection->reqType);
}

int OnFrameRecvCallback(nghttp2_session *session, const nghttp2_frame *frame, void *userData)
{
    LOGE("on_frame_recv_callback %d", frame->hd.type);
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    if (IsDestroyed(connection->fd)) {
        return 0;
    }
    if (frame->hd.type == NGHTTP2_GOAWAY) {
        LOGE("on_frame_recv_callback NGHTTP2_GOAWAY");
        HandleGoawayFrame(userData, frame->goaway);
    }
    return 0;
}

int OnStreamCloseCallback(nghttp2_session *session, int32_t streamId, uint32_t errorCode, void *userData)
{
    LOGE("on_stream_close_callback errcode %d", errorCode);
    const std::string map = UnorderedMapToJsonString(g_responseHeadersManagerMap);
    LOGE("on_stream_close_callback map %s", map.c_str());
    struct Connection *connection = nullptr;
    connection = static_cast<struct Connection *>(userData);
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_RESPONSE, const_cast<char *>(map.c_str()), 0, connection->reqType);
    g_responseHeadersManagerMap.clear();
    usleep(NUM_100);
    g_postEvent(HTTP_CALLBACK_TYPE_CLOSE, const_cast<char *>(std::to_string(errorCode).c_str()),
        0, connection->reqType);
    usleep(NUM_100);
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_END, nullptr, 0, connection->reqType);
    g_streamCloseManagerMap[connection->fd] = true;
    return 0;
}

int OnDataChunkRecvCallback(nghttp2_session *session, uint8_t flags, int32_t streamId, const uint8_t *data,
                            size_t len, void *userData)
{
    LOGE("on_data_chunk_recv_callback len:%d", len);
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    if (IsDestroyed(connection->fd)) {
        return 0;
    }
    LOGE("on_data_chunk_recv_callback reqType %d", connection->reqType);
    uint8_t* dataResult = const_cast<uint8_t*>(data);
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_RESPONSE_DATA_TRUNCK,
        static_cast<void*>(dataResult), len, connection->reqType);
    return 0;
}

int OnHeaderCallback(nghttp2_session *handle, const nghttp2_frame *frame, nghttp2_rcbuf *name,
                     nghttp2_rcbuf *value, uint8_t flags, void *userData)
{
    LOGE("OnHeaderCallback name %s value %s flags %d", nghttp2_rcbuf_get_buf(name).base,
         nghttp2_rcbuf_get_buf(value).base, flags);
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    if (IsDestroyed(connection->fd)) {
        return 0;
    }
    auto isNeed = g_needSetResponseHeadersMap.find(handle);
    if (isNeed == g_needSetResponseHeadersMap.end()) {
        g_responseHeadersManagerMap[(char *)nghttp2_rcbuf_get_buf(name).base]
        = (char *)nghttp2_rcbuf_get_buf(value).base;
    }
    return 0;
}

int OnNghttpError(nghttp2_session *handle, int errorCode, const char *message, size_t len,
                  void *userData)
{
    struct Connection *connection = nullptr;
    connection = (struct Connection *)userData;
    if (IsDestroyed(connection->fd)) {
        return 0;
    }
    LOGE("OnNghttpError errcode %d msg %s", errorCode, message);
    HttpStreamErrorCallback errCallBack;
    errCallBack.errCode = errorCode;
    errCallBack.msg = message;
    static std::unordered_map<std::string, std::string> map;
    map["message"] = (char *)message;
    map["code"] = (char *)std::to_string(errorCode).c_str();
    std::string result = UnorderedMapToJsonString(map);
    void* data = static_cast<void *>(const_cast<char*>(result.c_str()));
    g_postEvent(HTTP_STREAM_CALLBACK_TYPE_ERROR, data, 0, connection->reqType);
    g_streamCloseManagerMap[connection->fd] = true;
    return 0;
}

void SetupNghttp2Callbacks(nghttp2_session_callbacks *callbacks)
{
    nghttp2_session_callbacks_set_on_header_callback2(callbacks, OnHeaderCallback);
    nghttp2_session_callbacks_set_send_callback(callbacks, SendCallback);
    nghttp2_session_callbacks_set_recv_callback(callbacks, RecvCallback);
    nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, OnFrameRecvCallback);
    nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, OnStreamCloseCallback);
    nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, OnDataChunkRecvCallback);
    nghttp2_session_callbacks_set_error_callback2(callbacks, OnNghttpError);
}

void SubmitRequest(Connection *connection, std::string host, std::string path, std::string method,
                   std::string contentType)
{
    LOGE("submit_request path:%s host:%s method:%s contentType:%s", path.c_str(), host.c_str(), method.c_str(),
         contentType.c_str());
    // grpc请求不走这里 拦截
    if (contentType != "application/json") {
        return;
    }
    // MAKE_NV_CS 是动态传变量
    const nghttp2_nv nva[] = {
        MAKE_NV_CS(":method", method.c_str()),
        MAKE_NV_CS(":path", path.c_str()),
        MAKE_NV(":scheme", "https"),
        MAKE_NV_CS(":authority", host.c_str()),
        MAKE_NV("accept", "*/*"),
        MAKE_NV("user-agent", "nghttp2/" NGHTTP2_VERSION)
    };
    connection->reqType = 0;
    int streamId =
        nghttp2_submit_request(connection->session, nullptr, nva, sizeof(nva) / sizeof(nva[0]), nullptr, connection);
    if (streamId < 0) {
        LOGE("nghttp2_submit_request %d", streamId);
    }
}

void CtlPoll(struct pollfd *pollfd, nghttp2_session *session, int wantIo)
{
    pollfd->events = 0;
    if (nghttp2_session_want_read(session) || wantIo == WANT_READ) {
        pollfd->events |= POLLIN;
    }
    if (nghttp2_session_want_write(session) || wantIo == WANT_WRITE) {
        pollfd->events |= POLLOUT;
    }
}


int HandleIo(nghttp2_session *session, int fd)
{
    LOGE("handle_io session %p", session);
    if (!session || IsDestroyed(fd)) {
        return 0;
    }
    int rv = 0;
    rv = nghttp2_session_recv(session);
    if (rv != 0) {
        LOGE("nghttp2_session_recv %d", rv);
        return rv;
    }
    rv = nghttp2_session_send(session);
    if (rv != 0) {
        LOGE("nghttp2_session_send %d", rv);
    }
    return rv;
}


ssize_t DataSourceReadCallback(nghttp2_session *session, int32_t streamId, uint8_t *buf, size_t len,
                               uint32_t *dataFlags, nghttp2_data_source *source, void *userData)
{
    LOGE("data_source_read_callback start");
    *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
    uint8_t *bufferData = static_cast<uint8_t *>(source->ptr);
    Connection *connection = (struct Connection *)userData;
    LOGE("data_source_read_callback write_buff_len %d", connection->writeBuffLen);
    std::copy(bufferData, bufferData + connection->writeBuffLen, buf);
    return connection->writeBuffLen;
}


void SendData(Connection *connect, void *buff, int buffLen)
{
    LOGE("napi--->sendData fd %d host %s path %s buffLen %d", connect->fd, connect->host.c_str(), connect->path.c_str(),
         buffLen);
    const nghttp2_nv nva[] = {MAKE_NV(":method", "POST"),
                              MAKE_NV_CS(":path", connect->path.c_str()),
                              MAKE_NV(":scheme", "https"),
                              MAKE_NV_CS(":authority", connect->host.c_str()),
                              MAKE_NV("content-type", "application/grpc"),
                              MAKE_NV("accept", "*/*"),
                              MAKE_NV("te", "trailers"),
                              MAKE_NV("accept-encoding", "identity"),
                              MAKE_NV("grpc-accept-encoding", "identity"),
                              MAKE_NV("user-agent", "grpc-node-js/0.1.0" NGHTTP2_VERSION)};

    nghttp2_data_source reqBuffer;
    reqBuffer.ptr = buff;
    nghttp2_data_provider data_prvd = {reqBuffer, DataSourceReadCallback};
    connect->writeBuffLen = buffLen;
    connect->reqType = 1;
    int streamId =
        nghttp2_submit_request(connect->session, nullptr, nva, sizeof(nva) / sizeof(nva[0]), &data_prvd, connect);
    if (streamId < 0) {
        LOGE("sendData stream_id_ %d", streamId);
    }
}

int HandleSSLShake(Connection *connection, int fd, std::string host)
{
    SSL_CTX *sslCtx;
    SSL *ssl;
    sslCtx = SSL_CTX_new(TLS_client_method());
    if (sslCtx == nullptr) {
        LOGE("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), nullptr));
    }
    InitSslCtx(sslCtx);
    ssl = SSL_new(sslCtx);
    if (ssl == nullptr) {
        LOGE("SSL_new %s", ERR_error_string(ERR_get_error(), nullptr));
    }
    int rv = OpenSSLConnectHandle(ssl, fd, host);
    if (rv < 0) {
        LOGE("SSL/TLS handshake failed rv %d", rv);
        return rv;
    }
    connection->ssl = ssl;
    connection->sslCtx = sslCtx;
    connection->wantIo = IO_NONE;
    connection->fd = fd;
    MakeNonBlock(fd);
    SetTcpNodelay(fd);
    nghttp2_session_callbacks *callbacks;
    rv = nghttp2_session_callbacks_new(&callbacks);
    if (rv != 0) {
        LOGE("nghttp2_session_callbacks_new %d", rv);
        return rv;
    }
    SetupNghttp2Callbacks(callbacks);
    rv = nghttp2_session_client_new(&connection->session, callbacks, connection);
    nghttp2_session_callbacks_del(callbacks);
    if (rv != 0) {
        LOGE("nghttp2_session_client_new %d", rv);
        return rv;
    }
    rv = nghttp2_submit_settings(connection->session, NGHTTP2_FLAG_NONE, nullptr, 0);
    if (rv != 0) {
        LOGE("nghttp2_submit_settings %d", rv);
        return rv;
    }
    return rv;
}

void FreeResource(Connection *connection)
{
    LOGE("FreeResource fd %d", connection->fd);
    if (connection->session != nullptr) {
        nghttp2_session_del(connection->session);
        connection->session = nullptr;
    }
    if (connection->ssl != nullptr) {
        SSL_shutdown(connection->ssl);
        SSL_free(connection->ssl);
    }
    if (connection->sslCtx != nullptr) {
        SSL_CTX_free(connection->sslCtx);
    }
    shutdown(connection->fd, SHUT_WR);
    close(connection->fd);
}

void HandleRequest(RequestTask *requestTask)
{
    int fd = requestTask->fd;
    struct Connection connection;
    if (requestTask->contentType == "application/grpc") {
        LOGE("napi-->Request application/grpc");
        return;
    }
    int rv = HandleSSLShake(&connection, fd, requestTask->host);
    if (rv < 0) {
        LOGE("HandleSSLShake Error fd :%d", fd);
        return;
    }
    nfds_t npollfds = 1;
    struct pollfd pollfds[1];
    g_connectManagerMap[connection.fd] = &connection;
    SubmitRequest(&connection, requestTask->host, requestTask->path, requestTask->method,
        requestTask->contentType);
    pollfds[0].fd = requestTask->fd;
    CtlPoll(pollfds, connection.session, connection.wantIo);
    while (nghttp2_session_want_read(connection.session) || nghttp2_session_want_write(connection.session)) {
        if (IsDestroyed(fd)) {
            break;
        }
        int nfds = poll(pollfds, npollfds, -1);
        if (nfds == -1) {
            LOGE("poll %s", strerror(errno));
        }
        if (pollfds[0].revents & (POLLIN | POLLOUT)) {
            int res = HandleIo(connection.session, fd);
            if (res < 0) {
                break;
            }
        }
        if ((pollfds[0].revents & POLLHUP) || (pollfds[0].revents & POLLERR)) {
            LOGE("nghttp2_session_want_read error");
            break;
        }
        CtlPoll(pollfds, connection.session, connection.wantIo);
    }
    if (IsDestroyed(fd)) {
        return;
    }
    g_freeManagerMap[fd] = true;
    FreeResource(&connection);
}

void setCallBackRefMessage(const napi_ref &callbackRef) { callBackRefMessage_ = callbackRef; }

void setEnvMessage(const napi_env &env) { envMessage_ = env; }
#endif